﻿#region License
// Copyright 2007-2008 Rory Plaire (codekaizen@gmail.com)
/*
 * This license governs use of the accompanying software. If you use the software, you
 * accept this license. If you do not accept the license, do not use the software.
 * 
 * 1. Definitions
 * The terms "reproduce," "reproduction," "derivative works," and "distribution" have the
 * same meaning here as under U.S. copyright law.
 * A "contribution" is the original software, or any additions or changes to the software.
 * A "contributor" is any person that distributes its contribution under this license.
 * "Licensed patents" are a contributor's patent claims that read directly on its contribution.
 * 
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, including the license conditions 
 *     and limitations in section 3, each contributor grants you a non-exclusive, worldwide, 
 *     royalty-free copyright license to reproduce its contribution, prepare derivative works 
 *     of its contribution, and distribute its contribution or any derivative works that you create.
 * (B) Patent Grant- Subject to the terms of this license, including the license conditions and 
 *     limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free 
 *     license under its licensed patents to make, have made, use, sell, offer for sale, import, 
 *     and/or otherwise dispose of its contribution in the software or derivative works of the 
 *     contribution in the software.
 * 
 * 3. Conditions and Limitations
 * (A) No Trademark License- This license does not grant you rights to use any contributors' 
 *     name, logo, or trademarks.
 * (B) If you bring a patent claim against any contributor over patents that you claim are 
 *     infringed by the software, your patent license from such contributor to the software 
 *     ends automatically.
 * (C) If you distribute any portion of the software, you must retain all copyright, patent, 
 *     trademark, and attribution notices that are present in the software.
 * (D) If you distribute any portion of the software in source code form, you may do so only under 
 *     this license by including a complete copy of this license with your distribution. If you 
 *     distribute any portion of the software in compiled or object code form, you may only do so 
 *     under a license that complies with this license.
 * (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no 
 *     express warranties, guarantees or conditions. You may have additional consumer rights under 
 *     your local laws which this license cannot change. To the extent permitted under your local laws, 
 *     the contributors exclude the implied warranties of merchantability, fitness for a particular 
 *     purpose and non-infringement.
 * 
 */
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Threading;
using NPack.Interfaces;

namespace NPack
{
    internal class ManagedVectorExtent<TComponent, TVector> : SynchronizedStorage,
                                                              IEnumerable<ManagedVectorPage<TComponent, TVector>>
        where TComponent : IEquatable<TComponent>, IComparable<TComponent>,
                           IComputable<TComponent>, IConvertible, IFormattable
        where TVector : IBufferedVector<TComponent, TVector>, IEquatable<TVector>,
                        IComparable<TVector>, IComputable<Double, TVector>
    {
        [Flags]
        internal enum ManagedVectorExtentState
        {
            AddingVector = 2,
            IsFull = 4
        }

        internal const Int32 ExtentSizeInPages = 16;
        //private static readonly Int32 _vectorIdPageShift = (Int32)(Math.Log(ManagedVectorPage<TComponent, TVector>.PageSizeBytes) /
        //                                                           Math.Log(2));
        //private static readonly Int32 _vectorIdExtentShift = _vectorIdPageShift + (Int32)(Math.Log(ExtentSizeInPages) /
        //                                                                                  Math.Log(2));
        private readonly ManagedVectorPage<TComponent, TVector>[] _pages
            = new ManagedVectorPage<TComponent, TVector>[ExtentSizeInPages];

        private readonly UInt16 _extentId;
        private readonly Int32 _extentVectorLength;
        private readonly LinkedList<Int32> _pageFreeList = new LinkedList<Int32>();
        private Int32 _pageFreeMap;
        private Int32 _vectorCount;

        //private readonly IBufferedVectorFactory<TComponent, TVector> _factory;
        //private readonly IVectorBuffer<TComponent, TVector> _buffer;

        public ManagedVectorExtent(UInt16 extentId,
                                   Int32 extentVectorSize)//,
        //IBufferedVectorFactory<TComponent, TVector> factory,
        //IVectorBuffer<TComponent, TVector> buffer)
        {
            _extentId = extentId;
            _extentVectorLength = extentVectorSize;

            for (Int32 i = 0; i < ExtentSizeInPages; i++)
            {
                _pageFreeList.AddLast(i);
            }

            //_factory = factory;
            //_buffer = buffer;
        }

        public UInt16 ExtentId
        {
            get { return _extentId; }
        }

        public ManagedVectorPage<TComponent, TVector> this[Int32 vectorId]
        {
            get
            {
                //if (index < 0 || index >= 16)
                //{
                //    throw new ArgumentOutOfRangeException("index",
                //                                          index,
                //                                          "Must be between 0 and 15");
                //}

                //return _pages[index];

                BitVector32 id = new BitVector32(vectorId);
                Int32 pageId = id[ManagedVectorBuffer<TComponent, TVector>.PageSection];
                return _pages[pageId];
            }
        }

        // TODO: optimize the 2 component case if needed
        //public Int32 AddVector(TComponent v0, TComponent v1)
        //{
        //    throw new NotImplementedException();
        //}

        // TODO: optimize the 3 component case if needed
        //public Int32 AddVector(TComponent v0, TComponent v1, TComponent v2)
        //{
        //    throw new NotImplementedException();
        //}

        public Int32 AddVector(params TComponent[] components)
        {
            if (components.Length != _extentVectorLength)
            {
                String message = String.Format("Vector must have {0} components to exist on this extent, but has {1}",
                                               _extentVectorLength,
                                               components.Length);

                throw new ArgumentException(message);
            }

            // Get an available page to store the vector on
            Int32 pageId = 0;
            Int32 vectorId = 0;
            ManagedVectorPage<TComponent, TVector> page;

            Int32 pagesFree = Thread.VolatileRead(ref _pageFreeMap);

            if (pagesFree == 0)
            {
                // No free pages... add one
                page = AddPage();
                pageId = page.PageId;
            }
            else
            {
                for (Int32 i = 0; i < ExtentSizeInPages; i++)
                {
                    if (((pagesFree >> i) & 0x1) != 0)
                    {
                        pageId = i;
                        break;
                    }
                }

                page = _pages[pageId];
            }

            // Store the vector
            vectorId = page.AddVector(components);

            // Perform housekeeping
            Interlocked.Increment(ref _vectorCount);

            if (page.IsFull)
            {
                // Update page free information
                Int32 result;

                do
                {
                    pagesFree = Thread.VolatileRead(ref _pageFreeMap);
                    Int32 offBit = ~(0x1 << pageId);
                    result = Interlocked.CompareExchange(ref _pageFreeMap, pagesFree & offBit, pagesFree);
                } while (result != pagesFree);

                if (_pageFreeList.Count == 0)
                {
                    SetState((Int32)ManagedVectorExtentState.IsFull);
                }
            }

            // Build the vector id and return it
            BitVector32 id = new BitVector32(0);
            id[ManagedVectorBuffer<TComponent, TVector>.ExtentSection] = _extentId;
            id[ManagedVectorBuffer<TComponent, TVector>.PageSection] = pageId;
            id[ManagedVectorBuffer<TComponent, TVector>.VectorSection] = vectorId;

            return id.Data;
        }

        public ManagedVectorPage<TComponent, TVector> AddPage()
        {
            checkExtentFull();

            lock (_pageFreeList)
            {
                // double check
                checkExtentFull();

                Int32 currentSlot = _pageFreeList.First.Value;
                _pageFreeList.RemoveFirst();

                ManagedVectorPage<TComponent, TVector> page = new ManagedVectorPage<TComponent, TVector>((UInt16)currentSlot,
                                                                                                         _extentVectorLength);

                _pages[currentSlot] = page;

                Int32 pageMapBit = 0x1 << currentSlot;

                do
                {
                    Int32 currentPageMap = Thread.VolatileRead(ref _pageFreeMap);
                    Interlocked.CompareExchange(ref _pageFreeMap, pageMapBit | currentPageMap, currentPageMap);
                } while ((Thread.VolatileRead(ref _pageFreeMap) & pageMapBit) == 0);

                return page;
            }
        }

        public Boolean IsFull
        {
            get
            {
                return GetState((Int32) ManagedVectorExtentState.IsFull);
            }
        }

        public Int32 Count
        {
            get
            {
                return Thread.VolatileRead(ref _vectorCount);
            }
        }

        public Int32 VectorLength
        {
            get { return _extentVectorLength; }
        }

        public TComponent GetVectorComponent(Int32 index, Int32 componentIndex)
        {
            return this[index][index, componentIndex];
        }

        public void SetVector(TVector value, Int32 index)
        {
            throw new NotImplementedException();
        }

        #region Implementation of IEnumerable

        public IEnumerator<ManagedVectorPage<TComponent, TVector>> GetEnumerator()
        {
            foreach (ManagedVectorPage<TComponent, TVector> page in _pages)
            {
                if (page != null)
                {
                    yield return page;
                }
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        private void checkExtentFull()
        {
            if (_pageFreeList.Count == 0)
            {
                throw new InvalidOperationException("Extent is full.");
            }
        }
    }
}
